/*

Copyright (C) 2008 Victor Manuel Sanchez Cartagena, Juan Antonio Perez Ortiz


This file is part of ANTLRArbol.

ANTLRArbol is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

ANTLRArbol is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. 

*/

package org.ANTLRArbol.lib;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Set;


/**
 * 
 * Builds a parse tree and generates a graphical representation of it, 
 * written in DOT code.
 * 
 * @author vitaka
 *
 */
public class AttributeTreeANTLRArbol {
	
	/**
	 * Synthesized or Inherited attribute of a node.
	 * 
	 * @author vitaka
	 *
	 */
	private class Attribute
	{
		Attribute(String n, String v)
		{
			name=n;
			value=v;
		}
		/**
		 * Name of the attribute.
		 */
		public String name;
		
		/**
		 * Value of the attribute. The method ToString() is used to get it.
		 */
		public String value;
	}
	
	
	/**
	 * Node representation of a execution of a rule.
	 * 
	 * @author vitaka
	 *
	 */
	private class Node
	{	
		/**
		 * Unique name of the node.
		 * 
		 */
		private String name;
		
		/**
		 * Name of the rule that represents the node.
		 * 
		 */
		private String nameNotTerminal;
		
		/**
		 * Synthesized attributes
		 */
		private ArrayList<Attribute> synthesized;
		
		/**
		 * Inherited attributes 
		 */
		private ArrayList<Attribute> inherited; 

		Node(String pnombre, String pnombrent)
		{
			name=pnombre;
			nameNotTerminal=pnombrent;
			synthesized = new ArrayList<Attribute>();
			inherited = new ArrayList<Attribute>();
		}

		public ArrayList<Attribute> getSynthesized() {
			return synthesized;
		}

		public ArrayList<Attribute> getInherited() {
			return inherited;
		}
		

		public String getName() {
			return name;
		}

		public String getNameNotTerminal() {
			return nameNotTerminal;
		}

	}
	
	/**
	 * Represents an inherited attribute that depends on an attribute of another node.
	 * It is used because when a dependence of a inherited attriute is detected, we don't
	 * know the unique name of the node the attribute belongs to.
	 * When the name is known, the information is transferred to a "Dependency object".
	 * 
	 * @author vitaka
	 *
	 */
	private class InheritedDependency
	{
		/**
		 * Initial node of the arrow that represents the dependency. 
		 */
		public String initialNode;
		/**
		 * Initial attribute of the arrow that represents the dependency. 
		 */
		public String initialAttribute;
		
		/**
		 * Terminal attribute of the arrow that represents the dependency.
		 */
		public String terminalAttribute;
		
		/**
		 * Constructor
		 * 
		 * @param inNode Initial node of the edge that represents the dependency. 
		 * @param inAttr Initial attribute of the edge that represents the dependency
		 * @param terAttr Terminal attribute of the edge that represents the dependency.
		 */
		public InheritedDependency(String inNode, String inAttr,
				String terAttr) {
			super();
			this.initialNode = inNode;
			this.initialAttribute = inAttr;
			this.terminalAttribute = terAttr;
		}
	}
	
	/**
	 * 
	 * Represents a dependency between two attributes of different nodes.
	 * This depency will be displayed as an arrow.
	 * In this case, if A depends on B, the arrow will go from B to A.
	 * 
	 * @author vitaka
	 *
	 */
	private class Dependency
	{
		/**
		 * Initial node of the arrow that represents the dependency. 
		 */
		public String nodeFrom;
		
		/**
		 * Initial attribute of the arrow that represents the dependency. 
		 */
		public String attributeFrom;
		
		/**
		 * Terminal node of the arrow that represents the dependency.
		 */
		public String nodeTo;
		
		/**
		 * Terminal attribute of the arrow that represents the dependency.
		 */
		public String attributeTo;
		
		/**
		 * True if the terminal attribute is synthesized, false if it is inherited.
		 */
		public boolean isSynthesized; 
		
		/**
		 * 
		 * @param inNode Initial node of the arrow that represents the dependency
		 * @param inAttr Initial attribute of the arrow that represents the dependency. 
		 * @param terNode Terminal node of the arrow that represents the dependency
		 * @param terAttr Terminal attribute of the arrow that represents the dependency
		 * @param syn True if the terminal attribute is synthesized, false if it is inherited.
		 */
		public Dependency(String inNode, String inAttr,String terNode , String terAttr, boolean syn
				 ) {
			super();
			this.attributeTo = terAttr;
			this.attributeFrom = inAttr;
			this.nodeTo = terNode;
			this.nodeFrom = inNode;
			this.isSynthesized=syn;
		}
		
	}
	
	/**
	 * Represents a transition between two nodes. It isn't a depency.
	 * A transition from A to B means that one of the right-side variables of the rule A is the left-side variable of the rule B.
	 * And, for the input that we are processing, the rule B is called.  
	 * 
	 * @author vitaka
	 *
	 */
	private class Transition
	{
		/**
		 * Initial node
		 * 
		 */
		public Node from;
		
		/**
		 * Terminal node
		 */
		public Node to;
		
		/**
		 * Constructor
		 * 
		 * @param initial INitial node.
		 * @param terminal Terminal node.
		 */
		public Transition(Node initial, Node terminal) {
			super();
			this.to = terminal;
			this.from = initial;
		}
		
	}
	
	
	private int numNode;
	private String output;	
	private LinkedList<Node> nodeStack;
	private ArrayList<Transition> transitions;
	private ArrayList<Node> nodes;
	private Hashtable<String, String> nodeNamesVariables;
	private ArrayList<InheritedDependency> dependenciesInherited;
	private int maxAttributeLength;
	private Set<String> variablesToShow;
	private Set<Dependency> dependencies;
	private boolean showInheritedDep;
	private boolean showSynthesizedDep;
	
	/**
	 * Constructor
	 * 
	 * @param maxLengthAttribute Maximum length of an attribute value. Attributes whose value length is greater than this parameter will be shown partially.
	 * @param showSynDep True if we want to display arrows representing dependencies of synthesized attributes. False if we don't.
	 * @param showInhDep True if we want to display arrows representing dependencies of inherited attributes. False if we don't.
	 * @param args Variables whose attributes will be shown. If it is empty, attributes of every variable will be shown.
	 */
	public AttributeTreeANTLRArbol(int maxLengthAttribute,boolean showSynDep, boolean showInhDep, String... args  )
	{
		showInheritedDep=showInhDep;
		showSynthesizedDep=showSynDep;
		maxAttributeLength = maxLengthAttribute;
		output="";
		transitions=new ArrayList<Transition>();
		numNode=0;
		nodeStack = new LinkedList<Node>();
		nodes = new ArrayList<Node>();
		dependenciesInherited = new ArrayList<InheritedDependency>();
		variablesToShow= new HashSet<String>();
		dependencies = new HashSet<Dependency>();
		for(String var: args)
			variablesToShow.add(var);
		nodeNamesVariables = new Hashtable<String, String>();
	}
	
	/**
	 * Tells the parse tree that the input has matched a terminal rule
	 * @param terminalName Name of the terminal rule
	 * @param text Value of the attribute "text".
	 * @return Unique name of the node that represents this execution of the rule.
	 */
	public String terminal(String terminalName, String text)
	{
		String retorno = "n"+numNode;
		Node n = new Node(retorno,terminalName);
		numNode++;
		n.getSynthesized().add(new Attribute("text",text));
		
		
		if(!nodeStack.isEmpty())
			transitions.add(new Transition(nodeStack.getFirst(),n));
		nodes.add(n);
		nodeNamesVariables.put(n.getName(), n.getNameNotTerminal());
		
		return retorno;
	}
	
	/**
	 * Tells the parse tree that a not terminal rule ends
	 * 
	 * @param name Name of the rule
	 * @return Unique name of the node that represents this execution of the rule.
	 */
	public String startNotTerminal(String name)
	{
		output+="entramos "+name+"\n";
		String retorno = "n"+numNode;
		
		for(InheritedDependency d: dependenciesInherited)
		{
			//transicionesDependencias=transicionesDependencias+d.nodoOrigen+":"+d.atributoOrigen+" -> "+retorno+":"+d.atributoDestino+" [style=dotted, color=blue, constraint=false, weight=0.001] \n";
			dependencies.add(new Dependency(d.initialNode,d.initialAttribute,retorno,d.terminalAttribute,false));
		}
		
		Node nodo = new Node(retorno,name);
		numNode++;
		nodeStack.push(nodo);
		
		dependenciesInherited.clear();
		
		return retorno;
	}
	
	/**
	 * Tells the parse tree that the last not terminal rule has ended.
	 * 
	 */
	public void endNotTerminal()
	{
		
		Node n = nodeStack.pop();
		if(!nodeStack.isEmpty())
			transitions.add(new Transition(nodeStack.getFirst(),n));
		
		nodes.add(n);
		nodeNamesVariables.put(n.getName(), n.getNameNotTerminal());
		
	}
	
	/**
	 * Generates the DOT code to display the parse tree.
	 * 
	 * @return DOT code 
	 */
	public String generateOutput()
	{
		
		
		StringBuilder salidadot = new StringBuilder("digraph ANTLR {\nrankdir=TB\nordering=out\n");
						
		
		//Node generation
		for(int i=0; i<nodes.size();i++)
		{		
			Node n = nodes.get(i);
			
			
			salidadot.append(n.getName()).append(" [shape=box, label=< <TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\"><TR>");
			
			
			if(n.getInherited().size()>0 && ( variablesToShow.isEmpty() || variablesToShow.contains(n.getNameNotTerminal())))
			{
				salidadot.append("<TD><TABLE BORDER=\"1\" CELLBORDER=\"0\" CELLSPACING=\"0\"><TR><TD BORDER=\"1\">Inherited</TD></TR>");
				for(Attribute at: n.getInherited())
				{
					salidadot.append("<TR><TD PORT=\""+at.name+"\">"+at.name+": ");
					if(at.value.length()>maxAttributeLength && maxAttributeLength>0)
						salidadot.append(escape(at.value.substring(0,maxAttributeLength))).append(" (...)</TD></TR>");
					else
						salidadot.append(escape(at.value)).append("</TD></TR>");
				}
				salidadot.append("</TABLE></TD>");
			}
			
			salidadot.append("<TD BORDER=\"1\"><FONT POINT-SIZE=\"20\">").append(n.getNameNotTerminal()).append("</FONT></TD>");
			
			if(n.getSynthesized().size()>0 && ( variablesToShow.isEmpty() || variablesToShow.contains(n.getNameNotTerminal())))
			{
				salidadot.append("<TD><TABLE BORDER=\"1\" CELLBORDER=\"0\" CELLSPACING=\"0\"><TR><TD BORDER=\"1\">Synthesized</TD></TR>");
				for(Attribute at: n.getSynthesized())
				{
					salidadot.append("<TR><TD PORT=\""+at.name+"\">"+at.name+": ");
					if(at.value != null && at.value.length()>maxAttributeLength && maxAttributeLength>0)
						salidadot.append(escape(at.value.substring(0,maxAttributeLength))+" (...)</TD></TR>");
					else
						salidadot.append(escape(at.value)+"</TD></TR>");
				}
				salidadot.append("</TABLE></TD>");
			}
			salidadot.append("</TR></TABLE> >]\n");
			
		}
		
		//Transition generation
		for(int i=0; i<transitions.size(); i++)
		{
			Transition t = transitions.get(i);
			salidadot.append(t.from.name+" -> "+t.to.name+";\n");
		}
		
		
		//Subgraph generation. We use subgraphs to ensure that the node order is right
		for(int i=0; i<nodes.size(); i++)
		{
			Node n = nodes.get(i);
			ArrayList<String> nodosHijos= new ArrayList<String>();
			for(int j=0;j<transitions.size();j++)
			{
				Transition t = transitions.get(j);
				if(t.from==n)
					nodosHijos.add(t.to.name);
			}
			if(nodosHijos.size()>1)
			{
				salidadot.append("subgraph sub"+i+"\n{\nrank=same;\n");
				for(int k=0; k<nodosHijos.size();k++)
				{
					salidadot.append(nodosHijos.get(k));
					if(k<nodosHijos.size()-1)
						salidadot.append("->");
				}
				salidadot.append(" [style=invisible,dir=none];\n}\n");
			}
		}
		
		//Dependency arrows generation
		for(Dependency dep: dependencies)
		{
			if((dep.isSynthesized && showSynthesizedDep) || (!dep.isSynthesized && showInheritedDep))
			{
				boolean representable=false;
				String salidaOrigen="",salidaDestino="";
				if(variablesToShow.isEmpty())
				{
					representable=true;
					salidaOrigen=dep.nodeFrom+":"+dep.attributeFrom;
					salidaDestino=dep.nodeTo+":"+dep.attributeTo;
				}
				if(!representable)
				{
					String no,nd;
					no = nodeNamesVariables.get(dep.nodeFrom);
					nd = nodeNamesVariables.get(dep.nodeTo);
					if(no!=null && nd!=null)
					{
						if(variablesToShow.contains(no) || variablesToShow.contains(nd))
						{
							representable=true;
							
							if(variablesToShow.contains(no))
								salidaOrigen=dep.nodeFrom+":"+dep.attributeFrom;
							else
								salidaOrigen=dep.nodeFrom;
							
							if(variablesToShow.contains(nd))
								salidaDestino=dep.nodeTo+":"+dep.attributeTo;
							else
								salidaDestino=dep.nodeTo;
						}
					}
					
				}
				
				if(representable)
				{
					String color="blue";
					if(dep.isSynthesized)
						color="red";
					salidadot.append(  salidaOrigen+" -> "+salidaDestino+"[style=dotted, color="+color+", constraint=false, weight=0.001] \n");
				}
			}
		}
		
		salidadot.append("}\n");
		return salidadot.toString();
	}
	
	/**
	 * Tells the parse tree the value of a synthesized attribute of the last not terminal rule.
	 * 
	 * @param name Name of the attribute
	 * @param value Value of the attribute
	 */
	public void showInherited(String name, Object value)
	{

		String strValor;
		if(value!=null)
			strValor =value.toString();
		else
			strValor="null";
		
		
		output+="heredado "+name+": "+strValor+"\n";
		
		nodeStack.getFirst().getInherited().add(new Attribute(name,strValor));
	}
	
	/**
	 * Tells the parse tree the value of an synthesized attribute of the last not terminal rule.
	 * 
	 * @param name Name of the attribute
	 * @param value Value of the attribute
	 */
	public void showSynthesized(String name, Object value)
	{
		
		String strValor;
		if(value!=null)
			strValor =value.toString();
		else
			strValor="null";
		
		
		
		output+="sintetizado "+name+": "+strValor+"\n";
		
		nodeStack.getFirst().getSynthesized().add(new Attribute(name,strValor));
	}
	
	/**
	 * Tells the parse tree that a synthesized attribute of the current rule (the last started not terminal rule) depends on an attribute of another rule.
	 * 
	 * @param initialNode Name of the node that the synthesized attribute depends on.
	 * @param initialAttribute Name of the attribute that the synthesized attribute depends on.
	 * @param terminalAttribute Attribute of current rule
	 */
	public void addSynthesizedDependency(String initialNode, String initialAttribute, String terminalAttribute)
	{
		dependencies.add(new Dependency(initialNode,initialAttribute,nodeStack.getFirst().name,terminalAttribute,true));
	}
	
	/**
	 * Tells the parse tree that an inherited attribute of the current rule (the last started not terminal rule) depends on an attribute of its father rule.
	 * 
	 * @param initialAttribute Attribute of father rule
	 * @param terminalAttribute Attribute of current rule
	 */
	public void addInheritedDependencyFather(String initialAttribute,String terminalAttribute)
	{
		dependenciesInherited.add(new InheritedDependency(nodeStack.getFirst().name,initialAttribute,terminalAttribute));
	}
	
	/**
	 * Tells the parse tree that an inherited attribute of the current rule (the last started not terminal rule) depends on an attribute of a rule with the same father.
	 * 
	 * @param nodoOrigen Name of the node that the inherited attribute depends on.
	 * @param atributoOrigen Name of the attribute that the synthesized attribute depends on.
	 * @param atributoDestino Attribute of current rule
	 */
	public void addInheritedDependencySibling(String nodoOrigen, String atributoOrigen,String atributoDestino)
	{
		dependenciesInherited.add(new InheritedDependency(nodoOrigen,atributoOrigen,atributoDestino));
	}
	
	/**
	 * Escapes a text changing problematic characters to XML entities to avoid problems with dot output.
	 * @param input Text to escape.
	 * @return Escaped text.
	 */
	private String escape(String input)
	{
		if (input==null)
			return "null";
		else
		{
		String strValor=input;
		
		strValor = strValor.replaceAll("&","&amp;");
		strValor = strValor.replaceAll("<","&lt;");
		strValor = strValor.replaceAll(">","&gt;");
		strValor = strValor.replaceAll("'","&apos;");
		strValor = strValor.replaceAll("\"","&quot;");
		
		return strValor;
	}
	}
	
}
